package cbbx_sm.endtoend;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.List;

import org.junit.Test;

import cbbx_sm.decision_maker.search.DynamicProgrammingLookahead;
import cbbx_sm.decision_maker.search.DynamicProgrammingTableEntry;
import cbbx_sm.decision_maker.search.SchedulerState;
import cbbx_sm.decision_maker.search.States;
import cbbx_sm.decision_maker.search.WorldStateTransition;
import cbbx_sm.evaluation.CompareAlternativeSchedulers.Decision;
import cbbx_sm.parser.CameraData;
import cbbx_sm.probabilistic_model.Cluster;
import cbbx_sm.probabilistic_model.ProbabilisticModel;
import cbbx_sm.utils.ExperimentManager;
import cbbx_sm.utils.RandomGaussian;
import cbbx_sm.utils.Timestamp;

public class GaussianLargeScale {
	private static final String FILE_NAME_FORMAT = "data/cameraData/GaussianPassingEntity%s%s.txt";
	private static final int TIME_BETWEEN_PEOPLE = 10;
	private static int NUMBER_OF_PEOPLE = 10;
	private static int MAX_NUMBER_OF_CAMERAS = 10;
	private static int ZOOM_REGIONS = 2;
	private static int ENTITY_SIDE_SIZE = 1;
	private static int ENTITY_STEP = 1;
	private static Timestamp INITIAL_TIME = new Timestamp(20090526000000L);
	private static int RANDOM_SEED = 5;
	private static int numberOfSecondsCorrelation = 3;
	private static int numberOfStates = 10000;
	private static double utilityZoom = 1;
	private static double utilityUP = 0;
	private static double error = 0.01;
	private static boolean regenerate = true;
	private static boolean useApriori = true;
	private static boolean discounting = false;
	private static int timeInFuture = 0;
	private static String trainDay = "";
	private static String testDay = "";
	
	// Initialize the script before any test case is executed.
	private static ArrayList<String> generateScript(int numberOfCameras, int k) throws Exception {
		ArrayList<ArrayList<String>> allCamData = new ArrayList<ArrayList<String>>();
		ArrayList<String> cameraIds = new ArrayList<String>();
		for (int i = 0; i < numberOfCameras; i++) {
			cameraIds.add(String.valueOf(i));
			allCamData.add(new ArrayList<String>());
		}
		ArrayList<String> personInRegion = new ArrayList<String>();
		for (int i = 0; i < k; i++) {
			StringBuilder sb = new StringBuilder();
			// for each bounding box (all, face, torso, legs).
			for (int j=0; j<4; j++) {
                                       //bid   ulx  urx   brx   bry     r     g     b 
				sb.append(String.format("%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t",
						-1,
						(i * (ENTITY_STEP + ENTITY_SIDE_SIZE)),
						(i * (ENTITY_STEP + ENTITY_SIDE_SIZE)),
						(i * (ENTITY_STEP + ENTITY_SIDE_SIZE)) + ENTITY_SIDE_SIZE,
						(i * (ENTITY_STEP + ENTITY_SIDE_SIZE)) + ENTITY_SIDE_SIZE,
						1,
						1,
						1));				
			}
			personInRegion.add(sb.toString());
		}
		RandomGaussian gaussian = new RandomGaussian(RANDOM_SEED);
		double nextRegionMEAN = 4f;
		double nextRegionVARIANCE = 2f;
		

		Timestamp currentTime = Timestamp.getTimeStampInFuture(INITIAL_TIME, 1);
		for (int p = 0; p < NUMBER_OF_PEOPLE; p++) {
			currentTime = Timestamp.getTimeStampInFuture(currentTime, TIME_BETWEEN_PEOPLE);
			//int timeToFirstRegion = gaussian.getPositiveIntGaussian(firstRegionMEAN, firstRegionVARIANCE);
			//Timestamp currentTime = Timestamp.getTimeStampInFuture(INITIAL_TIME, timeToFirstRegion);
			int timeToGoToNextRegion = gaussian.getPositiveIntGaussian(nextRegionMEAN, nextRegionVARIANCE);

			for (int r = 0; r < numberOfCameras * k; r++) {
				// the time that the person stays in the same region.
				for (int t = 0; t < timeToGoToNextRegion; t++) {
					int currentCamera = (r / k);
					allCamData.get(currentCamera).add(
		                                // ts   eid   lines			
							String.format("%s\t%d\t%s",
									// timestamp _time.jpg
									String.format("%d_%s.jpg", currentCamera, currentTime), -1, personInRegion.get(r % k)));
					currentTime = Timestamp.getNextTimeStamp(currentTime);
				}
			}
		}

		EndToEndTestUtils.print(allCamData, FILE_NAME_FORMAT);
		return cameraIds;
	}
	
	@Test public void testCompareAlternatives() throws Exception {
		for (int numberOfCameras = 10; numberOfCameras <= MAX_NUMBER_OF_CAMERAS; numberOfCameras+=10) {
			ArrayList<String> cameraIds = generateScript(numberOfCameras, ZOOM_REGIONS);
			int numberOfTimeStampsLookAhead = 3;
			int k = ZOOM_REGIONS;
			
			/*
			EndToEndTestUtils.evaluateSchedule(cameraIds, FILE_NAME_FORMAT, k,
					numberOfTimeStampsLookAhead, numberOfSecondsCorrelation, numberOfStates, utilityZoom, utilityUP,
					discounting, error, regenerate, Decision.treeLookahead, useApriori, timeInFuture, trainDay, testDay);
			
			
			numberOfTimeStampsLookAhead = 1;
			
			EndToEndTestUtils.evaluateSchedule(cameraIds, FILE_NAME_FORMAT, k,
					numberOfTimeStampsLookAhead, numberOfSecondsCorrelation, numberOfStates, utilityZoom, utilityUP, floor,
					false, error, regenerate, Decision.lookahead, useApriori, timeInFuture, trainDay, testDay);
			
			numberOfTimeStampsLookAhead = 5;

			numberOfTimeStampsLookAhead = 1;

			EndToEndTestUtils.evaluateSchedule(cameraIds, FILE_NAME_FORMAT, k,
					numberOfTimeStampsLookAhead, numberOfSecondsCorrelation, numberOfStates, utilityZoom, utilityUP, discounting, 
					error, regenerate, Decision.lookahead, useApriori, timeInFuture, trainDay, testDay);
			*/
			numberOfTimeStampsLookAhead = 3;
			
		/*	EndToEndTestUtils.evaluateSchedule(cameraIds, FILE_NAME_FORMAT, k,
					numberOfTimeStampsLookAhead, numberOfSecondsCorrelation, numberOfStates, utilityZoom, utilityUP, discounting, 
					error, regenerate, Decision.treeLookahead, useApriori, timeInFuture, trainDay, testDay);
        */
			EndToEndTestUtils.evaluateSchedule(cameraIds, FILE_NAME_FORMAT, k,
					numberOfTimeStampsLookAhead, numberOfSecondsCorrelation, numberOfStates, utilityZoom, utilityUP, discounting, 
					error, regenerate, Decision.lookahead, useApriori, timeInFuture, trainDay, testDay);
			
			EndToEndTestUtils.evaluateSchedule(cameraIds, FILE_NAME_FORMAT, k,
					numberOfTimeStampsLookAhead, numberOfSecondsCorrelation, numberOfStates, utilityZoom, utilityUP, discounting, error, 
					regenerate, Decision.mostlikelyAlways, useApriori, timeInFuture, trainDay,  testDay);
			
			numberOfTimeStampsLookAhead= 1;
			EndToEndTestUtils.evaluateSchedule(cameraIds, FILE_NAME_FORMAT, k,
					numberOfTimeStampsLookAhead, numberOfSecondsCorrelation, numberOfStates, utilityZoom, utilityUP, discounting, error, 
					regenerate, Decision.lookahead, useApriori, timeInFuture, trainDay,  testDay);
			
			/*
			EndToEndTestUtils.evaluateSchedule(cameraIds, FILE_NAME_FORMAT, k,
					numberOfTimeStampsLookAhead, numberOfSecondsCorrelation, numberOfStates, utilityZoom, utilityUP, floor, discounting, error, regenerate, Decision.lookaheadLocalProb, trainDay, testDay);
			
			numberOfSecondsCorrelation = 1;
			EndToEndTestUtils.evaluateSchedule(cameraIds, FILE_NAME_FORMAT, k,
					numberOfTimeStampsLookAhead, numberOfSecondsCorrelation, numberOfStates, utilityZoom, utilityUP, floor, discounting, error, regenerate, Decision.lookaheadLocalProb, trainDay, testDay);
			
			
			EndToEndTestUtils.evaluateSchedule(cameraIds, FILE_NAME_FORMAT, k,
					numberOfTimeStampsLookAhead, numberOfSecondsCorrelation, numberOfStates, utilityZoom, utilityUP, floor, error, regenerate, Decision.rr, trainDay, testDay);
		    */
		}
	}
	@Test public void testCompare1LookaheadTo10() throws Exception {
		int numberOfCameras = 1;
		int zoomRegions = 2;
		WorldStateTransition wst = getGaussianWorldStateTransition(numberOfCameras, zoomRegions);
		States states1 = new States(wst, ExperimentManager.usePreComputedGrid);
		int numberOfTimeStampsLookAhead = 1;
		DynamicProgrammingTableEntry[] result1Lookahead = DynamicProgrammingLookahead.populateScoreArray(wst, states1,numberOfTimeStampsLookAhead,false,error, numberOfStates, utilityZoom, utilityUP);
		
		States states3 = new States(wst, ExperimentManager.usePreComputedGrid);
		numberOfTimeStampsLookAhead = 10;
		DynamicProgrammingTableEntry[] result3Lookahead =
			DynamicProgrammingLookahead.populateScoreArray(
					wst, states3,numberOfTimeStampsLookAhead,false,error, numberOfStates, utilityZoom, utilityUP);
		System.out.printf("%s\t%s\t%s\t%s\t%n", "1 LookaheadScore", "3 LookaheadAcore", "1 LookaheadAction", "3 LookaheadAction");
		for (double[] state: states1){
			if (result1Lookahead[states1.get(state)].bestActionFloor.compareTo(result3Lookahead[states1.get(state)].bestActionFloor) != 0) {
				System.out.printf("%s\t%f\t%f\t%s\t%s\t%n", 
						new SchedulerState(state),
						result1Lookahead[states1.get(state)].scoreFloor,
						result3Lookahead[states1.get(state)].scoreFloor,
						result1Lookahead[states1.get(state)].bestActionFloor,
						result3Lookahead[states1.get(state)].bestActionFloor);
				
			}
		}
	}
	
	@Test public void testCompareMostLikeleyToLookahead() throws Exception {
		int numberOfCameras = 1;
		int zoomRegions = 2;
		WorldStateTransition wst = getGaussianWorldStateTransition(numberOfCameras, zoomRegions);
		States states1 = new States(wst, ExperimentManager.usePreComputedGrid);
		int numberOfTimeStampsLookAhead = 1;
		DynamicProgrammingTableEntry[] resultLookahead = DynamicProgrammingLookahead.populateScoreArray(wst, states1,numberOfTimeStampsLookAhead,false,error, numberOfStates, utilityZoom, utilityUP);
		
		System.out.printf("%d%s\t%s\t%s\t%n", numberOfTimeStampsLookAhead, " LookaheadScore", "LookaheadAction", "MostLikelyAction");
		for (double[] state: states1){
				System.out.printf("%s\t%f\t%s\t%s\t%n", 
						new SchedulerState(state),
						resultLookahead[states1.get(state)].scoreFloor,
						//result3Lookahead[states1.get(state)].scoreFloor,
						resultLookahead[states1.get(state)].bestActionFloor,
						highestProbState(state, wst));
						//result3Lookahead[states1.get(state)].bestActionFloor);
		}
	}

	private String highestProbState(double[] state, WorldStateTransition wst) {
		int bestIndex = -1;
		double highestProb = -1;
		
		for (int i = 0; i < state.length; i++) {
			double s = state[i];
			if (s > highestProb) {
				highestProb = s;
				bestIndex = i;
			}
		}
		return wst.getModel().getFusedClusters().get(bestIndex).getUniqueId();
	}

	public static WorldStateTransition getGaussianWorldStateTransition(
			int numberOfCameras, int zoomRegions) throws Exception {
		ArrayList<String> cameraIds = generateScript(numberOfCameras, zoomRegions);

		// Load camera data used for training.
		LinkedHashMap<String, CameraData> camDataMap = EndToEndTestUtils.loadCameraData(FILE_NAME_FORMAT, cameraIds, trainDay);
		List<CameraData> camDataList = new ArrayList<CameraData>(camDataMap.values());
		
		// Each camera has a cluster.
		Hashtable<String, List<Cluster>> camClusters = EndToEndTestUtils.getCamClusters(cameraIds, camDataMap, zoomRegions);
		
		// All the clusters together.
		List<Cluster> fusedClusters = new ArrayList<Cluster>();
		for (String cam: cameraIds){
			fusedClusters.addAll(camClusters.get(cam));
		}
		
		// Load Probabilistic Model.
		ProbabilisticModel probModel =
			new ProbabilisticModel(camDataList, fusedClusters, numberOfSecondsCorrelation);
		probModel.setThreshold(0.0);
		System.out.println("Probabilities computed.");
		System.out.println(probModel.toString());
		
		return new WorldStateTransition(probModel);
	}

}
